package com.ruoyi.framework.security.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.exception.BaseException;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.config.RuoYiConfig;
import com.ruoyi.framework.config.ServerConfig;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.applet.domain.Order;
import com.ruoyi.project.applet.domain.Wallet;
import com.ruoyi.project.applet.mapper.OrderMapper;
import com.ruoyi.project.applet.service.IWalletService;
import com.ruoyi.project.system.domain.ByteArrayMultipartFile;
import com.ruoyi.project.system.domain.SysUserWx;
import com.ruoyi.project.system.domain.vo.WXLoginRequest;
import com.ruoyi.project.system.domain.vo.WeChatRes;
import com.ruoyi.project.system.service.ISysUserWxService;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
public class SysLoginService {

    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserWxService sysUserWxService;

    @Autowired
    IWalletService walletService;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    private OrderMapper orderMapper;

    @Value("${wx.code2Session}")
    private String wxCode2Session;

    @Value("${wx.getPhoneNumber}")
    private String wxPhoneNumber;

    @Value("${wx.wxAppId}")
    private String wxAppid;

    @Value("${wx.wxSecret}")
    private String wxSecret;

    @Value("${wx.grantType}")
    private String wxGrantType;

    @Value("${wx.getUnlimitedQRCode}")
    private String qrCode;

    @Value("${wx.link}")
    private String link;

    @Autowired
    private ServerConfig serverConfig;

    private static final Logger log = LoggerFactory.getLogger(SysLoginService.class);

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaException();
        }
        // 用户验证
        Authentication authentication = null;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new CustomException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token
        return tokenService.createToken(loginUser);
    }

    public String wxLogin(WXLoginRequest wxRequest) {
        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
        WeChatRes weChatRes = getOpenID(wxRequest.getCode());

        SysUserWx exist = sysUserWxService.selectSysUserWxByOpenId(weChatRes.getOpenid());
        SysUserWx sysUserWx = new SysUserWx();

        //判断账号是否停用
        if (exist != null) {
            Assert.isTrue(!Constants.STATUS_FAULT.equals(exist.getStatus()), "账号已冻结，请联系管理员");
        }

        if (wxRequest.getNickName() != null) {
            sysUserWx.setNickName(wxRequest.getNickName());
        }
        sysUserWx.setLoginIp(ipAddr);
        sysUserWx.setStatus(UserConstants.NORMAL);

        if (exist == null) {
            sysUserWx.setAvatar(wxRequest.getImage());
            sysUserWx.setOpenId(weChatRes.getOpenid());
            sysUserWx.setUnionId(weChatRes.getUnionid());
            sysUserWx.setCreateTime(new Date());
            sysUserWx.setParentId(wxRequest.getParentId());
            final String max = sysUserWxService.getMax();
//            String STR_FORMAT = "0000000";
//            DecimalFormat df = new DecimalFormat(STR_FORMAT);
//            String serialId = df.format(Integer.parseInt(max) + 1);
            String number = generateRandomCode();
            sysUserWx.setNumberId(number);
            sysUserWxService.insertSysUserWx(sysUserWx);
            //生成钱包信息
            Wallet wallet = new Wallet();
            wallet.setUserId(weChatRes.getOpenid());
            wallet.setMoney("0");
            wallet.setNumber(0);
            walletService.insertWallet(wallet);
        } else {
            sysUserWx.setAvatar(wxRequest.getImage());
            sysUserWx.setOpenId(exist.getOpenId());
            sysUserWx.setUnionId(exist.getUnionId());
            sysUserWx.setUserId(exist.getUserId());
            sysUserWx.setPhonenumber(exist.getPhonenumber());
            if (!StringUtils.hasText(exist.getParentId())) {
                sysUserWx.setParentId(wxRequest.getParentId());
            }

            sysUserWxService.updateSysUserWx(sysUserWx);
        }
        LoginUser loginUser = new LoginUser();
        loginUser.setOpenId(sysUserWx.getOpenId());
        loginUser.setIpaddr(ipAddr);


        //如果有的话设置
        loginUser.setUserWx(sysUserWx);
        loginUser.setUserType(Constants.STATUS_FAULT);
        String token = tokenService.createTokenWx(loginUser);
        SysUserWx update = new SysUserWx();
        update.setUserId(sysUserWx.getUserId());
        update.setCheckToken(token);
        update.setLoginIp(ipAddr);
        update.setLoginDate(new Date());
        sysUserWxService.updateSysUserWx(update);
        return token;
    }

    public  String generateRandomCode() {
        // 创建字符源：大写字母 A-Z 和 数字 0-9
        String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String digits = "0123456789";

        List<Character> letterList = new ArrayList<>();
        for (char c : letters.toCharArray()) {
            letterList.add(c);
        }

        List<Character> digitList = new ArrayList<>();
        for (char c : digits.toCharArray()) {
            digitList.add(c);
        }

        // 随机选择 2 个字母
        Collections.shuffle(letterList);
        List<Character> selectedLetters = letterList.subList(0, 2);

        // 随机选择 7 个数字
        Collections.shuffle(digitList);
        List<Character> selectedDigits = digitList.subList(0, 7);

        // 合并字母和数字并打乱顺序
        List<Character> combined = new ArrayList<>(selectedLetters);
        combined.addAll(selectedDigits);
        Collections.shuffle(combined);

        // 构建结果字符串
        StringBuilder result = new StringBuilder();
        for (char c : combined) {
            result.append(c);
        }

        return result.toString();
    }


    /**
     * 小程序使用账号密码登录
     */
    public String wxLoginByAccount(WXLoginRequest wxRequest) {
        String userType = wxRequest.getUserType();
        Assert.hasText(userType, "用户类型不能为空");
        String phonenumber = wxRequest.getPhonenumber();
        Assert.hasText(phonenumber, "账号不能为空");
        String password = wxRequest.getPassword();
        Assert.hasText(password, "密码不能为空");
        SysUserWx sysUserWx = sysUserWxService.selectSysUserWxByPhone(phonenumber);
        Assert.notNull(sysUserWx, "当前手机号未注册，请联系管理员或使用微信登录");
        Assert.isTrue(userType.equals(sysUserWx.getUserType()), "当前账号未注册该平台");
        Assert.isTrue(!Constants.STATUS_FAULT.equals(sysUserWx.getStatus()), "账号已被停用，请联系管理员");
        Assert.isTrue(SecurityUtils.matchesPassword(password, sysUserWx.getPassword()), "密码错误");
        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
        //如果账号未绑定微信,则绑定
        if (!StringUtils.hasText(sysUserWx.getOpenId())) {
            WeChatRes weChatRes = getOpenID(wxRequest.getCode());
            String openid = weChatRes.getOpenid();
            SysUserWx userWxByOpenId = sysUserWxService.selectSysUserWxByOpenId(openid);
            Assert.isNull(userWxByOpenId, "当前微信已经绑定过手机号，请勿重复绑定");
            sysUserWx.setOpenId(openid);
            sysUserWx.setUnionId(weChatRes.getUnionid());
            sysUserWx.setAvatar(wxRequest.getImage());
            sysUserWx.setNickName(wxRequest.getNickName());
        }
        //如果已经绑定则直接登录
        LoginUser loginUser = new LoginUser();
        loginUser.setOpenId(sysUserWx.getOpenId());
        loginUser.setIpaddr(ipAddr);
       /* try {
            String realAddressByIP = AddressUtils.getRealAddressByIP(ipAddr);
            loginUser.setLoginLocation(realAddressByIP);
        } catch (Exception e) {

        }*/

        loginUser.setUserWx(sysUserWx);
        loginUser.setUserType(Constants.STATUS_FAULT);
        String token = tokenService.createTokenWx(loginUser);
        sysUserWx.setLoginIp(ipAddr);
        sysUserWx.setLoginDate(new Date());
        sysUserWx.setCheckToken(token);
        sysUserWxService.updateSysUserWx(sysUserWx);
        return token;
    }

    public SysUserWx checkWX(WXLoginRequest wxRequest) {
        //微信授权登录校验
        WeChatRes weChatRes = getOpenID(wxRequest.getCode());
        SysUserWx sysUserWx = sysUserWxService.selectSysUserWxByOpenId(weChatRes.getOpenid());
        return sysUserWx;

    }

    private WeChatRes getOpenID(String code) {
        Assert.hasText(code, "code不能为空");
        String appid = wxAppid;
        String secret = wxSecret;
        String url = String.format(wxCode2Session, appid, secret, code, wxGrantType);
        HttpHeaders httpHeaders = new HttpHeaders();
        HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        String response = responseEntity.getBody();
        WeChatRes weChatRes = JSON.parseObject(response, WeChatRes.class);
        Assert.notNull(weChatRes, "用户信息获取失败");

        if (weChatRes.getErrcode() != 0) {
            throw new BaseException(weChatRes.toString());
        }
        return weChatRes;
    }

    public WeChatRes getPhoneNumber(String code) {
        HttpHeaders httpHeaders = new HttpHeaders();
        JSONObject codeJson = new JSONObject();
        codeJson.put("code", code);
        HttpEntity<String> httpEntity = new HttpEntity<>(codeJson.toJSONString(), httpHeaders);
        String url = wxPhoneNumber + getAccessToken();
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        String response = responseEntity.getBody();
        WeChatRes weChatRes = JSON.parseObject(response, WeChatRes.class);

        System.out.println(response);
        if (weChatRes == null || weChatRes.getErrcode() != 0) {
            throw new BaseException(weChatRes == null ? "手机号获取失败" : weChatRes.toString());
        }

        return weChatRes;
    }


    private String getAccessToken() {

        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
        url = String.format(url, wxAppid, wxSecret);

        HttpEntity<String> httpEntity = new HttpEntity<>(new HttpHeaders());
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        String response = responseEntity.getBody();
        JSONObject jsonObject = JSON.parseObject(response);

        return jsonObject.getString("access_token");
    }

    public AjaxResult getLink(){
        String token = getAccessToken();
        String url = link + token;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("page_url","pages/index/index");
        jsonObject.put("is_permanent",false);
        HttpEntity<JSONObject> httpEntity = new HttpEntity<>(jsonObject, new HttpHeaders());
        ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        JSONObject parseObject = JSONObject.parseObject(result.getBody());
        if ("0".equals(parseObject.getString("errcode"))) {
            return AjaxResult.success(parseObject.get("link"));
        } else {
            throw new BaseException(parseObject.get("errmsg").toString());
        }

    }

    public AjaxResult getUnlimitedQRCode(String userId) {
        String accessToken = getAccessToken();
        String url = qrCode + accessToken;

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("page", "pages/index/index");
        jsonObject.put("scene", userId);
        jsonObject.put("check_path", false);
        //要打开的小程序版本。正式版为 "release"，体验版为 "trial"，开发版为 "develop"。默认是正式版。
        jsonObject.put("env_version", "release");

        String imgUrl = null;
        try {
            byte[] bytes = getWechatQrcodeByOkhttp3(url, jsonObject);
            if (bytes == null) {
                throw new BaseException("【小程序码】Failed to retrieve QR code.");
            }
            //也可以上传服务器或资源管理oss等，返回图片链接url

            MultipartFile multipartFile = new ByteArrayMultipartFile(bytes, "file", "qrcode.png", "image/png");
            String filePath = RuoYiConfig.getUploadPath();
            String fileName = FileUploadUtils.upload(filePath, multipartFile);
            imgUrl = serverConfig.getUrl() + fileName;
        } catch (Exception e) {
            log.error("【小程序码】请求失败:{}", e.toString());
            return AjaxResult.error("请求小程序码失败");
        }
        return AjaxResult.success("成功", imgUrl);


    }


    public byte[] getWechatQrcodeByOkhttp3(String url, JSONObject jsonBody) {
        OkHttpClient client = new OkHttpClient();
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonBody.toString());
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful() && response.body() != null) {
                return response.body().bytes();
            } else {
                log.error("【小程序码】Failed response, status code: {}", response.code());
            }
        } catch (IOException e) {
            log.error("【小程序码】Request failed", e);
        }
        return null;

    }
}